#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

// Specific to emulator
#include <fcntl.h>

// New includes
#include "util.h"
#include "network.h"
#include "queue.h"
#include "forward_table.h"

/*
 * THIS IS THE EMULATOR
 *
 * emulator -p <port> -q <queue_size> -f <filename> -l <log>
 *
 */

// CONFIGURATION VARIABLES
#define NUM_EMULATOR_ARGS 9

int in_port;          // Port on which the sender waits for requests
int queue_size;       // Size of each of the three queues
char *filename;       // File name containing the forwarding table
char *logname;
//FILE *logfile;

int out_port;         // Port on which the requester is waiting
int rate;             // Number of packets to be sent per second
uint32_t seq_no;      // Initial sequence of the packet exhange
uint32_t length;      // Length of the payload in the packets

// DATA STRUCTURES
Queue *prior_1;
Queue *prior_2;
Queue *prior_3;
Forward_Table *table;

// SOCKET VARIABLES
fd_set readfds;
int sockfd;
int sockflags;
int parse_code;
struct sockaddr_in servaddr, cliaddr;

// TIMEOUT FOR SELECT
#define TIMEOUT_SEC 0
#define TIMEOUT_USEC 0

void dg_send(int sockfd, struct sockaddr_in *pcliaddr, socklen_t clilen)
{
    int n;
    socklen_t len;
    int size = 0; // Used to keep track of total data sent
    int status;
    int queue_status;

    Em_Packet *recv_packet = NULL;       // Packet received
    char *tmp_str;

    // CURRENT PACKET
    char curr_buf[MAX_PACKET_SIZE];
    Em_Packet *curr_packet = NULL;       // Packet being delayed
    Table_Element *curr_table_entry = NULL;
    long curr_start = 0;

    //Host_Element *curr_next_hop;  // Next hop for curr delayed packet

    struct timeval timeout;

    while(1)
    {
      //Might need to to recvfrom reset... (see requester code)

      //      printf("Start of new loop: ");

        // Prepare to select and then select
        FD_ZERO(&readfds);
        FD_SET(sockfd, &readfds);
	timeout.tv_sec = TIMEOUT_SEC;    // Set timeout
	timeout.tv_usec = TIMEOUT_USEC;  // Set timeout
        status = select(sockfd+1, &readfds, NULL, NULL, &timeout);

	//	printf("%d\n", status);

        if(status > 0)
        {
	  /////////////////////////////////////////
	  // vvv NEED TO HOOK THINGS UP HERE vvv //
	  /////////////////////////////////////////

            printf("READY TO RECVFROM!\n");
            n = recvfrom(sockfd, em_packet_buf, MAX_PACKET_SIZE,
                         0, (struct sockaddr*)pcliaddr, &len);

        //setup timespec struct for nanosleep method
        //struct timespec rate_spec;
	//calculate_rate(rate, &rate_spec);



            //read packet from buffer
            recv_packet = (Em_Packet*)em_packet_buf;
	    packet = (Packet*)recv_packet->payload;

            if (n == -1)
            {
                perror(ERROR_RECVFROM);
                exit(1);
            }

            print_time();
	    size += n;

            em_packet_buf[n] = '\0'; // Null terminate

	    print_em_packet(recv_packet);
            print_recv_packet(packet, (struct sockaddr_in*)pcliaddr, n, size); // SEND IN THE EM_PACKET->PACKET

	    printf("AFTER PRINT RECV PACKET\n");


	  /////////////////////////////////////////
	  // ^^^ NEED TO HOOK THINGS UP HERE ^^^ //
	  /////////////////////////////////////////


	    // CHECK FORWARDING TABLE
	    tmp_str = inet_ntoa(recv_packet->dest_IP);
	    Table_Element *valid = scan_table(table, tmp_str,
                                   recv_packet->dest_port);
            if (valid == NULL)
            {
                file_print(logname, recv_packet, LOG_NO_ENTRY);
                printf("LOG_NO_ENTRY written to log\n");
                continue;
            }

	    // QUEUE PACKET ACCORDING TO PRIORITY LEVEL
            switch(recv_packet->priority)
            {
                case HIGH:
                    status = enqueue(prior_1, em_packet_buf, n);
                    if (status == -1)
                    {
                        file_print(logname, recv_packet, LOG_FULL_QUEUE1);
                        printf("LOG_FULL_QUEUE1 written to log\n");
                        continue;
                    }
                    break;
                case MED:
                    status = enqueue(prior_2, em_packet_buf, n);
                    if (status == -1)
                    {
                        file_print(logname, recv_packet, LOG_FULL_QUEUE2);
                        printf("LOG_FULL_QUEUE2 written to log\n");
                        continue;
                    }
                    break;
                case LOW:
                    status = enqueue(prior_3, em_packet_buf, n);
                    if (status == -1)
                    {
                        file_print(logname, recv_packet, LOG_FULL_QUEUE3);
                        printf("LOG_FULL_QUEUE3 written to log\n");
                        continue;
                    }
                    break;
                default:
                    perror(ERROR_RECVFROM);
                    exit(1);
	    }
        }

        // SELECT BLEW UP
	else if (status == -1)
        {
            printf("Some error occured on select\n");
        }

	// IF NOTHING TO SELECT
        else
        {
            // CHECK IF CURR_PACKET IS BEING DELAYED
            if(curr_packet != NULL)
            {
	      long now = get_milli_time();

                // Check if delay has expired
                if((now - curr_start) >=
                       curr_table_entry->delay)
                {

		  printf("\t\t\t If delay has expired (diff: %ld, delay: %d)...\n", now-curr_start, curr_table_entry->delay);
                    // > if delay has expired (and is not END),
                    //   randomly determine whether to drop

                    if(((Packet*)(curr_packet->payload))->type != 'E' &&
                        bool_rand(curr_table_entry->drop_prob))
                    {
                        file_print(logname, recv_packet, LOG_LOSS_EVENT);
                        printf("LOG_LOSS_EVENT written to log\n");

                        // Reset curr_packet information
                        curr_packet = NULL;
                        curr_table_entry = NULL;
                        curr_start = 0;

                        continue;
		    }

                    // Get forwarding table next hop entry
                    tmp_str = inet_ntoa(recv_packet->dest_IP);
                    curr_table_entry = scan_table(table, tmp_str, 
                                       recv_packet->dest_port);
                    if (curr_table_entry == NULL)
                    {
                        file_print(logname, recv_packet, LOG_NO_ENTRY);
                        printf("LOG_NO_ENTRY written to log\n");
                        continue;
                    }

                    // Setup outgoing send
                    struct hostent *outgoing;
                    outgoing = gethostbyname(curr_table_entry->
                                             next_hop.host_name);
                    bzero(pcliaddr, sizeof(*pcliaddr));
                    pcliaddr->sin_family = AF_INET;
                    pcliaddr->sin_port = htons(curr_table_entry->
                                               next_hop.port);
                    bcopy((char *)outgoing->h_addr,
                          (char *)&(pcliaddr->sin_addr.s_addr),
                          outgoing->h_length);

                    // RELEASE THE KRAKEN (i.e.
                    // FORWARD PACKET TO NEXT HOP)
                    int n;
                    print_time();
                    if ((n = sendto(sockfd, em_packet_buf,
                        sizeof(Em_Packet) + curr_packet->length,
                        0,(struct sockaddr*) pcliaddr, clilen)) == -1)
                    {
                        perror(ERROR_SENDTO);
                        exit(1);
                    }

		    print_em_packet((Em_Packet*)em_packet_buf);
                    print_sent_packet((Packet*)((Em_Packet*)em_packet_buf)->payload, pcliaddr, n);

                    printf("ARE WE REACHING THE RESET?\n");

		    // Reset curr_packet information
                    curr_packet = NULL;
                    curr_table_entry = NULL;
                    curr_start = 0;
		}

                // If delay has not expired, continue
	        else
                {
                    continue;
                }
	    }

            // IF NO CURR_PACKET
            else
	    {
                // Check priority level 1
                queue_status = dequeue(prior_1, curr_buf,
                                       MAX_PACKET_SIZE);

                // Check priority level 2
		if(queue_status == -1)
                {
                    queue_status = dequeue(prior_2, curr_buf,
                                           MAX_PACKET_SIZE);
		}

                // Check priority level 3
		if(queue_status == -1)
                {
                    queue_status = dequeue(prior_3, curr_buf,
                                           MAX_PACKET_SIZE);
		}

                // If all queues empty, continue
		if(queue_status == -1)
                {
                    curr_packet = NULL;
                    curr_table_entry = NULL;
                    curr_start = 0;
                    continue;
		}

                curr_packet = (Em_Packet*)curr_buf;
                tmp_str = inet_ntoa(curr_packet->dest_IP);
                curr_table_entry = scan_table(table, tmp_str,
                                              recv_packet->dest_port);
                curr_start = get_milli_time();
	    }
	}
    } // End infinite while
}

int main(int argc, char *argv[])
{
    int main_status;

    printf("SIZE: %ld\n", sizeof(struct in_addr) * 8);

    //printf("TIME: %ld\n", get_milli_time());

    //sleep(3);

    //printf("TIME: %ld\n", get_milli_time());

    // Verify number of command line arguments
    if (argc != NUM_EMULATOR_ARGS)
    {
        perror(ERROR_EMULATOR_USAGE);
        exit(1);
    }

    // Parse command line information and set up configurations
    while ((parse_code = getopt(argc, argv, "pqfl:")) != -1)
    {
       switch (parse_code)
       {
           case 'p':
	       in_port = strtol(argv[optind], NULL, 10);
	       if (in_port <= 1024 || in_port >= 65536)
	       {
                   perror(ERROR_PORT_RANGE);
                   exit(1);
	       }
               break;
           case 'q':
               queue_size = strtol(argv[optind], NULL, 10);
               break;
           case 'f':
	     filename = argv[optind];
               break;
           case 'l':
	     logname = optarg;
               break;
           default:
               perror(ERROR_EMULATOR_USAGE);
	       exit(1);
       }
    }
    printf ("\033[34mEMULATOR SETUP WITH THE FOLLOWING CONFIG:\033[1;34m\n> in_port:\t%d\n> queue_size:\t%d\n> filename:\t%s\n> logname:\t%s\033[0m\n", in_port, queue_size, filename, logname);

    // Setup forwarding table
    table = init_table(table, in_port);
    main_status = populate_table(table, filename);
    if(main_status == -1)
    {
        perror(ERROR_POPULATE);
        exit(1);
    }
    dump_table(table);

    // Setup queues
    prior_1 = NULL;
    prior_2 = NULL;
    prior_3 = NULL;
    prior_1 = init_queue(prior_1, queue_size);
    prior_2 = init_queue(prior_2, queue_size);
    prior_3 = init_queue(prior_3, queue_size);

    // Create socket
    if ( ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) )
    {
        perror(ERROR_SOCKET);
        exit(1);
    }

    // Make socket non-blocking
    sockflags = fcntl(sockfd, F_GETFL);
    sockflags = sockflags | O_NONBLOCK;
    fcntl(sockfd, F_SETFL, sockflags);    

    // Prepare the in-bound port
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(in_port);

    // Bind the socket
    if (bind(sockfd, (struct sockaddr *) &servaddr,
             sizeof(servaddr)) < 0)
    {
        perror(ERROR_BIND);
        exit(1);
    }

    dg_send(sockfd, &cliaddr, sizeof(cliaddr));

    return 0;
}
